/*
 *  Copyright (c) 2014 James Buncle
 * 
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 * 
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 * 
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 * 
 */
package com.jbuncle.seleniumiderunner;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.script.ScriptException;

/**
 *
 * @author James Buncle
 */
public class Test {

    private final String action;
    private final String value;
    private final String target;

    public Test(final String target, final String action, final String value) {
        this.target = target;
        this.action = action;
        this.value = value;
    }

    public String getAction() {
        return action;
    }

    public String getValue() {
        return value;
    }

    public String getTarget() {
        return target;
    }

    @Override
    public String toString() {
        return this.getAction() + ":" + this.getTarget() + ":" + this.getValue();
    }

    /**
     * Creates and returns a thread to execute test on given driver
     *
     * @param driver TestDriver to run test against
     * @param throwables list to add any thrown errors onto to report up
     * @return new thread to execute test in
     */
    private Thread createThreadForTest(final TestDriver driver, final List<Throwable> throwables) {
        return new Thread() {

            @Override
            public void run() {
                try {
                    driver.runCommand(action, getTarget(), getValue());
                } catch (final Throwable ex) {
                    throwables.add(ex);
                }
            }

        };

    }

    /**
     * Run test on given drivers. Executes on each on a new thread to run
     * asynchronously
     *
     * @param drivers
     * @throws ScriptException
     * @throws ReflectiveOperationException
     */
    protected void run(final Iterable<TestDriver> drivers)
            throws ScriptException, ReflectiveOperationException {
        final List<Thread> threads = new LinkedList<>();
        final List<Throwable> thrownErrors = Collections.synchronizedList(new LinkedList<Throwable>());

        for (final TestDriver driver : drivers) {
            final Thread thread = createThreadForTest(driver, thrownErrors);
            thread.start();
            threads.add(thread);

        }
        waitForThreadsToComplete(threads);
        if (!thrownErrors.isEmpty()) {
            throw new RuntimeException(thrownErrors.get(0));
        }
    }

    /**
     * Joins given threads, holding up the current thread until the given ones
     * are completed
     *
     * @param threads list of thread to wait on until finished
     */
    private void waitForThreadsToComplete(final Iterable<Thread> threads) {
        for (final Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException ex) {
            }
        }
    }

}
